package ws.performancetest.tests.synchronization;

import java.sql.ResultSet;
import ws.performancetest.tests.synchronization.enumaration.TestType;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import ws.performancetest.api.AbstractTestSuite;
import ws.performancetest.api.exception.TestSuiteSetupException;
import ws.performancetest.tests.synchronization.db.DBCategory;
import ws.performancetest.tests.synchronization.db.DBTest;
import ws.performancetest.tests.synchronization.db.DBTestResultManager;
import ws.performancetest.tests.synchronization.db.IDBRegister;
import ws.performancetest.tests.synchronization.db.IDBWriter;
import ws.performancetest.tests.synchronization.enumaration.SyncMechanism;
import ws.performancetest.tests.synchronization.lock.RLockImpl;
import ws.performancetest.tests.synchronization.lock.RWLockImpl;
import ws.performancetest.tests.synchronization.lock.SemImpl;
import ws.performancetest.tests.synchronization.lock.SharedList;
import ws.performancetest.tests.synchronization.lock.SyncImpl;
import ws.performancetest.tests.synchronization.task.TConsumer;
import ws.performancetest.tests.synchronization.task.TProducer;
import ws.performancetest.tests.synchronization.task.TReader;
import ws.performancetest.tests.synchronization.task.TWriter;
import ws.performancetest.tests.synchronization.task.TaskCreator;
import ws.utils.jdbc.DBConnection;
import ws.utils.jdbc.DBManager;

/**
 * This class runs a test group.
 *
 * <p>
 * The tests within a group are performed by several threads.
 * The threads concurrently try access some shared memory.
 * The threads are created by a thread pool object.
 * </p>
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class SynchronizationTest extends AbstractTestSuite {

  /** Test group identifier. */
  private String testGroup;
  /** List with tests to be performed. */
  private List<DBTest> testList;

  /* Object used to create task threads. */
  private TaskCreator taskcreator;
  /** Object used to write the current test results to the database. */
  private IDBWriter testWriter;
  /** Object used to add one test result to list of current results. */
  private IDBRegister testRegister;
  /** List shared among the threads. */
  private SharedList sharedlist;
  /** 
   * Each created thread will perform its own action {@code actionRepetition} times.
   */
  private int actionRepetition;

  /**
   * Create a new instance.
   *
   * @param testGroup Test group identifier. This is the name of the group of tests
   * that will be performed.
   * @param testGroup_Host_id pair (testGroup, Host) that owns the results list.
   *
   * @throws SQLException if can't create database writer or register.
   */
  public SynchronizationTest(String testGroup, int testGroup_Host_id, int actionRepetition) throws TestSuiteSetupException {
    try {
      testWriter = DBTestResultManager.createDBWriter();
      testRegister = DBTestResultManager.createIDBRegister();
      
      this.testGroup = testGroup;
      this.testList = getTestGroupTestList(testGroup);
      this.actionRepetition = actionRepetition;
      testWriter.setResultsOwner(testGroup_Host_id);
    } catch (Exception ex) {
      throw new TestSuiteSetupException(ex);
    }
  }

  /**
   * Run the test group.
   *
   * @throws SQLException if an database error occurred while saving test results.
   * @throws InterruptedException if interrupted while waiting for tests to finish.
   */
  @Override
  public void runAllTests() throws SQLException, InterruptedException {
    System.out.println("Running: " + testGroup);

    for (DBTest test : testList) {
      System.out.println("Current test: " + test.getID());

      taskcreator = new TaskCreator(actionRepetition, testRegister, test.getNumberOfThreads(), test.getNumberOfThreads());

      if (test.getSyncMec().equals(SyncMechanism.reentrantReadWriteLock)) {
        sharedlist = new RWLockImpl();
        taskcreator.setList(sharedlist);

        DBCategory category = test.getCategory();

        /* TestType can only be TestType.READ_WRITE_LOCK, so there's no need to check the test type... */
        /* Create half TWriters and half TReaders. */
        taskcreator.createTasks(category.getNumberOfReaders() * test.getNumberOfThreads() / 100, test.getID(), TReader.class.getName());
        taskcreator.createTasks(category.getNumberOfWriters() * test.getNumberOfThreads() / 100, test.getID(), TWriter.class.getName());

      } else {
        if (test.getSyncMec().equals(SyncMechanism.monitor)) {
          sharedlist = new SyncImpl();
        } else if (test.getSyncMec().equals(SyncMechanism.semaphore)) {
          sharedlist = new SemImpl();
        } else if (test.getSyncMec().equals(SyncMechanism.reentrantLock)) {
          sharedlist = new RLockImpl();
        }
        taskcreator.setList(sharedlist);
        if (test.getTestType() == TestType.UNDIFFERENTIATED_RW) {
          /* Create half TWriters and half TReaders. */
          taskcreator.createTasks(test.getNumberOfThreads() / 2, test.getID(), TWriter.class.getName());
          taskcreator.createTasks(test.getNumberOfThreads() / 2, test.getID(), TReader.class.getName());
        } else if (test.getTestType() == TestType.UNDIFFERENTIATED_PC) {
          /* Create half TWriters and half TReaders. */
          taskcreator.createTasks(test.getNumberOfThreads() / 2, test.getID(), TProducer.class.getName());
          taskcreator.createTasks(test.getNumberOfThreads() / 2, test.getID(), TConsumer.class.getName());

        }
      }
      /* Wait for the test's tasks to be performed. */
      taskcreator.waitForTasksToFinish();
      /* Notify when test finished. */
      synchronized (this) {
        notifyAll();
      }
    }

    /* Finally write the test to the database. */
    testWriter.writeTestResultsToBD();

    System.out.println("Test group: '" + testGroup + "' finished!");
  }

  @Override
  public int getTestCount() {
    return testList.size();
  }

  /**
   * Get the test list for a given test group.
   *
   * @param selectedTest test group.
   *
   * @return list with all tests for the test group.
   *
   * @throws SQLException
   */
  private List<DBTest> getTestGroupTestList(String selectedTest) throws SQLException {
    List<DBTest> dbTestList = new LinkedList<DBTest>();
    Statement stmt = null;
    ResultSet rs = null;
    String sql = "SELECT TestId, NumberOfThreads, SyncMec, TestType, GroupId, NumberOfReaders, NumberOfWriters FROM VTestList "
            + "WHERE GroupName='" + selectedTest + "'";

    stmt = DBConnection.getDBCon().createStatement();
    rs = stmt.executeQuery(sql);

    while (rs.next()) {
      DBTest test = new DBTest();
      test.setID(rs.getInt(1));
      test.setNumberOfThreads(rs.getInt(2));
      test.setSyncMec(rs.getString(3));
      test.setTestType(TestType.valueOf(rs.getString(4)));
      test.setGroupId(rs.getInt(5));
      int readers = rs.getInt(6);
      int writers = rs.getInt(7);

      test.setCategory(new DBCategory(readers, writers));

      dbTestList.add(test);
    }
    return dbTestList;
  }
}
